/**
 * TableDnD plug-in for JQuery, allows you to drag and drop table rows
 * You can set up various options to control how the system will work
 * Copyright (c) Denis Howlett <denish@isocra.com>
 * Licensed like jQuery, see http://docs.jquery.com/License.
 *
 * Configuration options:
 *
 * onDragStyle
 *     This is the style that is assigned to the row during drag. There are limitations to the styles that can be
 *     associated with a row (such as you can't assign a border--well you can, but it won't be
 *     displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as
 *     a map (as used in the jQuery css(...) function).
 * onDropStyle
 *     This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations
 *     to what you can do. Also this replaces the original style, so again consider using onDragClass which
 *     is simply added and then removed on drop.
 * onDragClass
 *     This class is added for the duration of the drag and then removed when the row is dropped. It is more
 *     flexible than using onDragStyle since it can be inherited by the row cells and other content. The default
 *     is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your
 *     stylesheet.
 * onDrop
 *     Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table
 *     and the row that was dropped. You can work out the new order of the rows by using
 *     table.rows.
 * onDragStart
 *     Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the
 *     table and the row which the user has started to drag.
 * onDragStop
 *     Pass a function that will be called when the user stops dragging regardless of if the rows have been
 *     rearranged. The function takes 2 parameters: the table and the row which the user was dragging.
 * onAllowDrop
 *     Pass a function that will be called as a row is over another row. If the function returns true, allow
 *     dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under
 *     the cursor. It returns a boolean: true allows the drop, false doesn't allow it.
 * scrollAmount
 *     This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the
 *     window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2,
 *     FF3 beta
 * dragHandle
 *     This is a jQuery mach string for one or more cells in each row that is draggable. If you
 *     specify this, then you are responsible for setting cursor: move in the CSS and only these cells
 *     will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where
 *     the whole row is draggable.
 *
 * Other ways to control behaviour:
 *
 * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows
 * that you don't want to be draggable.
 *
 * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form
 * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have
 * an ID as must all the rows.
 *
 * Other methods:
 *
 * $("...").tableDnDUpdate()
 * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells).
 * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again.
 * The table maintains the original configuration (so you don't have to specify it again).
 *
 * $("...").tableDnDSerialize()
 * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be
 * called from anywhere and isn't dependent on the currentTable being set up correctly before calling
 *
 * Known problems:
 * - Auto-scoll has some problems with IE7  (it scrolls even when it shouldn't), work-around: set scrollAmount to 0
 *
 * Version 0.2: 2008-02-20 First public version
 * Version 0.3: 2008-02-07 Added onDragStart option
 *                         Made the scroll amount configurable (default is 5 as before)
 * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes
 *                         Added onAllowDrop to control dropping
 *                         Fixed a bug which meant that you couldn't set the scroll amount in both directions
 *                         Added serialize method
 * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row
 *                         draggable
 *                         Improved the serialize method to use a default (and settable) regular expression.
 *                         Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
 * Version 0.6: 2011-12-02 Added support for touch devices
 * Version 0.7  2012-04-09 Now works with jQuery 1.7 and supports touch, tidied up tabs and spaces
 */
define(function(require, exports, moudles) {
    var jQuery = require('jquery');
    /*require('table');*/
    require('bootstrap-table-reorder-rows-css');
    /*require('bootstrapTableTree');*/
    require('bootstrapTableReorder');

    !function ($, window, document, undefined) {
        var dragObjs=[];
// Determine if this is a touch device
        var hasTouch = 'ontouchstart' in document.documentElement,
            startEvent = 'touchstart mousedown',
            moveEvent = 'touchmove mousemove',
            endEvent = 'touchend mouseup';

// If we're on a touch device, then wire up the events
// see http://stackoverflow.com/a/8456194/1316086
        hasTouch
        && $.each("touchstart touchmove touchend".split(" "), function (i, name) {
            $.event.fixHooks[name] = $.event.mouseHooks;
        });


        $(document).ready(function () {
            function parseStyle(css) {
                var objMap = {},
                    parts = css.match(/([^;:]+)/g) || [];
                while (parts.length)
                    objMap[parts.shift()] = parts.shift().trim();

                return objMap;
            }

            $('table').each(function () {
                if ($(this).data('table') == 'dnd') {

                    $(this).tableDnD({
                        onDragStyle: $(this).data('ondragstyle') && parseStyle($(this).data('ondragstyle')) || null,
                        onDropStyle: $(this).data('ondropstyle') && parseStyle($(this).data('ondropstyle')) || null,
                        onDragClass: $(this).data('ondragclass') == undefined && "tDnD_whileDrag" || $(this).data('ondragclass'),
                        onDrop: $(this).data('ondrop') && new Function('table', 'row', $(this).data('ondrop')), // 'return eval("'+$(this).data('ondrop')+'");') || null,
                        onDragStart: $(this).data('ondragstart') && new Function('table', 'row', $(this).data('ondragstart')), // 'return eval("'+$(this).data('ondragstart')+'");') || null,
                        onDragStop: $(this).data('ondragstop') && new Function('table', 'row', $(this).data('ondragstop')),
                        scrollAmount: $(this).data('scrollamount') || 5,
                        sensitivity: $(this).data('sensitivity') || 10,
                        hierarchyLevel: $(this).data('hierarchylevel') || 0,
                        indentArtifact: $(this).data('indentartifact') || '<div class="indent">&nbsp;</div>',
                        autoWidthAdjust: $(this).data('autowidthadjust') || true,
                        autoCleanRelations: $(this).data('autocleanrelations') || true,
                        jsonPretifySeparator: $(this).data('jsonpretifyseparator') || '\t',
                        serializeRegexp: $(this).data('serializeregexp') && new RegExp($(this).data('serializeregexp')) || /[^\-]*$/,
                        serializeParamName: $(this).data('serializeparamname') || false,
                        dragHandle: $(this).data('draghandle') || null
                    });
                }


            });
        });

        jQuery.tableDnD = {
            /** Keep hold of the current table being dragged
             * 保持当前的表被拖动 CXW*/
            currentTable: null,
            /** Keep hold of the current drag object if any */
            dragObject: null,
            /** The current mouse offset */
            mouseOffset: null,
            /** Remember the old value of X and Y so that we don't do too much processing */
            oldX: 0,
            oldY: 0,

            /** Actually build the structure
             * 实际建造结构*/
            build: function (options) {
                // Set up the defaults if any
                // 设置默认值，如果有的话 CXW


                this.each(function () {
                    // This is bound to each matching table, set up the defaults and override with user options
                    // 这将绑定到每个匹配表，设置默认值并覆盖用户选项。 CXW
                    this.tableDnDConfig = $.extend({
                        onDragStyle: null,
                        onDropStyle: null,
                        // Add in the default class for whileDragging
                        // 在拖动时添加默认的类 CXW
                        onDragClass: "tDnD_whileDrag",
                        onDrop: null,
                        onDragStart: null,
                        onDragStop: null,
                        scrollAmount: 5,
                        /** Sensitivity setting will throttle the trigger rate for movement detection
                         * 灵敏度设置将降低运动检测的触发率。CXW*/
                        sensitivity: 10,
                        /** Hierarchy level to support parent child. 0 switches this functionality off */
                        hierarchyLevel: 0,
                        /** The html artifact to prepend the first cell with as indentation */
                        indentArtifact: '<div class="indent">&nbsp;</div>',
                        /** Automatically adjust width of first cell */
                        autoWidthAdjust: true,
                        /** Automatic clean-up to ensure relationship integrity */
                        autoCleanRelations: true,
                        /** Specify a number (4) as number of spaces or any indent string for JSON.stringify */
                        jsonPretifySeparator: '\t',
                        /** The regular expression to use to trim row IDs */
                        serializeRegexp: /[^\-]*$/,
                        /** If you want to specify another parameter name instead of the table ID */
                        serializeParamName: false,
                        /** If you give the name of a class here, then only Cells with this class will be draggable */
                        dragHandle: null
                    }, options || {});

                    // Now make the rows draggable
                    $.tableDnD.makeDraggable(this);
                    // Prepare hierarchy support
                    this.tableDnDConfig.hierarchyLevel
                    && $.tableDnD.makeIndented(this);
                });

                // Don't break the chain
                return this;
            },
            makeIndented: function (table) {
                var config = table.tableDnDConfig,
                    rows = table.rows,
                    firstCell = $(rows).first().find('td:first')[0],
                    indentLevel = 0,
                    cellWidth = 0,
                    longestCell,
                    tableStyle;

                if ($(table).hasClass('indtd'))
                    return null;

                tableStyle = $(table).addClass('indtd').attr('style');
                $(table).css({whiteSpace: "nowrap"});

                for (var w = 0; w < rows.length; w++) {
                    if (cellWidth < $(rows[w]).find('td:first').text().length) {
                        cellWidth = $(rows[w]).find('td:first').text().length;
                        longestCell = w;
                    }
                }
                $(firstCell).css({width: 'auto'});
                for (w = 0; w < config.hierarchyLevel; w++)
                    $(rows[longestCell]).find('td:first').prepend(config.indentArtifact);
                firstCell && $(firstCell).css({width: firstCell.offsetWidth});
                tableStyle && $(table).css(tableStyle);

                for (w = 0; w < config.hierarchyLevel; w++)
                    $(rows[longestCell]).find('td:first').children(':first').remove();

                config.hierarchyLevel
                && $(rows).each(function () {
                    indentLevel = $(this).data('level') || 0;
                    indentLevel <= config.hierarchyLevel
                    && $(this).data('level', indentLevel)
                    || $(this).data('level', 0);
                    for (var i = 0; i < $(this).data('level'); i++)
                        $(this).find('td:first').prepend(config.indentArtifact);
                });

                return this;
            },
            /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
            makeDraggable: function (table) {
                var config = table.tableDnDConfig;

                config.dragHandle
                // We only need to add the event to the specified cells
                && $(config.dragHandle, table).each(function () {
                    // The cell is bound to "this"
                    $(this).bind(startEvent, function (e) {
                        $.tableDnD.initialiseDrag($(this).parents('tr')[0], table, this, e, config);
                        return false;
                    });
                })
                // For backwards compatibility, we add the event to the whole row
                // get all the rows as a wrapped set
                || $(table.rows).each(function () {
                    // Iterate through each row, the row is bound to "this"
                    if (!$(this).hasClass("nodrag")) {
                        $(this).bind(startEvent, function (e) {
                            if (e.target.tagName == "TD") {
                                /*$.tableDnD.initialiseDrag(this, table, this, e, config);*/
                                var flagId=this.id.split("_")[3];
                                dragObjs=[];
                                dragObjs[0]=this;

                                for(var i=0;i<table.rows.length;i++){
                                    var pid=table.rows[i].id.split("_")[2];
                                    if(flagId===pid){
                                        dragObjs.push(table.rows[i]);
                                        getRow(table,table.rows[i].id.split("_")[3]);
                                    }
                                }
                                function getRow(table,id){
                                    for(var s=0;s<table.rows.length;s++){
                                        var spid;
                                        var sid;
                                        var flag=true;
                                        var srow;
                                        for(var d=0;d<dragObjs.length;d++){
                                            if(dragObjs[d].id==table.rows[s].id){
                                                srow=table.rows[s];
                                                spid=table.rows[s].id.split("_")[2];
                                                sid=table.rows[s].id.split("_")[3];
                                                flag=false;

                                            }
                                        }
                                        if(flag){
                                            if(id===spid){
                                                dragObjs.push(srow);
                                                getRow(table,sid);
                                            }
                                        }
                                    }
                                    return false;
                                }

                                $.tableDnD.initialiseDrag(dragObjs, table, dragObjs, e, config);
                                return false;
                            }
                        }).css("cursor", "move"); // Store the tableDnD object
                    } else {
                        $(this).css("cursor", ""); // Remove the cursor if we don't have the nodrag class
                    }
                });
            },
            currentOrder: function () {
                var rows = this.currentTable.rows;
                return $.map(rows, function (val) {
                    var lll=($(val).data('level') + val.id).replace(/\s/g, '');
                    return lll;
                }).join('');
            },
            initialiseDrag: function (dragObject, table, target, e, config) {
               /* var flagId=dragObject.id.split("_")[3];
                dragObjs=[];
                dragObjs[0]=dragObject;

                var index=0;
                for(var i=0;i<table.rows.length;i++){
                    var pid=table.rows[i].id.split("_")[2];
                    if(flagId===pid){
                        dragObjs[index+1]=table.rows[i];
                        index++;
                    }
                }*/
                this.dragObject = dragObject;
                this.currentTable = table;
                this.mouseOffset = this.getMouseOffset(target, e);
                this.originalOrder = this.currentOrder();
                // Now we need to capture the mouse up and mouse move event
                // We can use bind so that we don't interfere with other event handlers
                $(document)
                    .bind(moveEvent, this.mousemove)
                    .bind(endEvent, this.mouseup);

                // Call the onDragStart method if there is one
                config.onDragStart
                && config.onDragStart(table, target);
            },
            updateTables: function () {
                this.each(function () {
                    // this is now bound to each matching table
                    if (this.tableDnDConfig)
                        $.tableDnD.makeDraggable(this);
                });
            },
            /** Get the mouse coordinates from the event (allowing for browser differences) */
            mouseCoords: function (e) {
                if (e.originalEvent.changedTouches)
                    return {
                        x: e.originalEvent.changedTouches[0].clientX,
                        y: e.originalEvent.changedTouches[0].clientY
                    };

                if (e.pageX || e.pageY)
                    return {
                        x: e.pageX,
                        y: e.pageY
                    };

                return {
                    x: e.clientX + document.body.scrollLeft - document.body.clientLeft,
                    y: e.clientY + document.body.scrollTop - document.body.clientTop
                };
            },
            /** Given a target element and a mouse eent, get the mouse offset from that element.
             To do this we need the element's position and the mouse position */
            getMouseOffset: function (target, e) {
                var mousePos,
                    docPos;

                e = e || window.event;

                docPos = this.getPosition(target);
                mousePos = this.mouseCoords(e);

                return {
                    x: mousePos.x - docPos.x,
                    y: mousePos.y - docPos.y
                };
            },
            /** Get the position of an element by going up the DOM tree and adding up all the offsets */
            getPosition: function (element) {
                var left = 0,
                    top = 0;

                // Safari fix -- thanks to Luis Chato for this!
                // Safari 2 doesn't correctly grab the offsetTop of a table row
                // this is detailed here:
                // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
                // the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
                // note that firefox will return a text node as a first child, so designing a more thorough
                // solution may need to take that into account, for now this seems to work in firefox, safari, ie
                var ele="";
                if(element.length>1){
                    ele=element[0];
                    if (ele.offsetHeight == 0)
                        ele = ele.firstChild; // a table cell

                    while (ele.offsetParent) {
                        left += ele.offsetLeft;
                        top += ele.offsetTop;
                        ele = ele.offsetParent;
                    }

                    left += ele.offsetLeft;
                    top += ele.offsetTop;

                    return {
                        x: left,
                        y: top
                    };
                }else{
                    if(element instanceof Array){
                        ele=element[0];
                    }else{
                        ele=element;
                    }

                    if (ele.offsetHeight == 0)
                        ele = ele.firstChild; // a table cell

                    while (ele.offsetParent) {
                        left += ele.offsetLeft;
                        top += ele.offsetTop;
                        ele = ele.offsetParent;
                    }

                    left += ele.offsetLeft;
                    top += ele.offsetTop;

                    return {
                        x: left,
                        y: top
                    };
                }


            },
            autoScroll: function (mousePos) {
                var config = this.currentTable.tableDnDConfig,
                    yOffset = window.pageYOffset,
                    windowHeight = window.innerHeight
                        ? window.innerHeight
                        : document.documentElement.clientHeight
                            ? document.documentElement.clientHeight
                            : document.body.clientHeight;

                // Windows version
                // yOffset=document.body.scrollTop;
                if (document.all)
                    if (typeof document.compatMode != 'undefined'
                        && document.compatMode != 'BackCompat')
                        yOffset = document.documentElement.scrollTop;
                    else if (typeof document.body != 'undefined')
                        yOffset = document.body.scrollTop;

                mousePos.y - yOffset < config.scrollAmount
                && window.scrollBy(0, -config.scrollAmount)
                || windowHeight - (mousePos.y - yOffset) < config.scrollAmount
                && window.scrollBy(0, config.scrollAmount);

            },
            moveVerticle: function (moving, currentRow) {

                var dobj="";
                if(this.dragObject instanceof Array){
                    dobj=this.dragObject[0];
                }else{
                    dobj=this.dragObject;
                }
                if (0 != moving.vertical
                    // If we're over a row then move the dragged row to there so that the user sees the
                    // effect dynamically
                    && currentRow
                    && dobj != currentRow
                    && dobj.parentNode == currentRow.parentNode){
                   if(this.dragObject.length>0){
                       var startRow= currentRow;
                        //从此处开始
                            if(1==moving.vertical){
                                for(var i=0;i<this.dragObject.length;i++) {
                                    var strDragObject = "";
                                    strDragObject = this.dragObject[i];
                                    0 < moving.vertical
                                    && strDragObject.parentNode.insertBefore(strDragObject, currentRow);
                                }
                            }else{
                                var reverseArr = this.dragObject;
                                reverseArr=reverseArr.reverse();
                                for(var i=0;i<reverseArr.length;i++) {
                                    var strDragObject = "";
                                    strDragObject = reverseArr[i];
                                    strDragObject.parentNode.insertBefore(strDragObject, startRow.nextSibling);
                                }
                                this.dragObject.reverse();
                               /* if(flag){
                                    console.log( startRow.nextSibling)
                                   strDragObject.parentNode.insertBefore(strDragObject, startRow.nextSibling);
                                    flag=false;
                                }else{
                                    console.log( startRow.nextSibling)
                                    strDragObject.parentNode.insertBefore(strDragObject, startRow.nextSibling);
                                }*/

                            }
                    }else{
                        0 > moving.vertical
                        && dobj.parentNode.insertBefore(dobj, currentRow.nextSibling)
                        || 0 < moving.vertical
                        && dobj.parentNode.insertBefore(dobj, currentRow);
                    }


                }


            },
            moveHorizontal: function (moving, currentRow) {
                var config = this.currentTable.tableDnDConfig,
                    currentLevel;

                if (!config.hierarchyLevel
                    || 0 == moving.horizontal
                    // We only care if moving left or right on the current row
                    || !currentRow
                    || this.dragObject != currentRow)
                    return null;

                currentLevel = $(currentRow).data('level');

                0 < moving.horizontal
                && currentLevel > 0
                && $(currentRow).find('td:first').children(':first').remove()
                && $(currentRow).data('level', --currentLevel);

                0 > moving.horizontal
                && currentLevel < config.hierarchyLevel
                && $(currentRow).prev().data('level') >= currentLevel
                && $(currentRow).children(':first').prepend(config.indentArtifact)
                && $(currentRow).data('level', ++currentLevel);

            },
            mousemove: function (e) {
                var dragObj = $($.tableDnD.dragObject),
                    config = $.tableDnD.currentTable.tableDnDConfig,
                    currentRow,
                    mousePos,
                    moving,
                    x,
                    y;
                e && e.preventDefault();
                if (!$.tableDnD.dragObject)
                    return false;

                // prevent touch device screen scrolling
                e.type == 'touchmove'
                && event.preventDefault(); // TODO verify this is event and not really e
                // update the style to show we're dragging
                config.onDragClass
                && dragObj.addClass(config.onDragClass)
                || dragObj.css(config.onDragStyle);
                mousePos = $.tableDnD.mouseCoords(e);
                x = mousePos.x - $.tableDnD.mouseOffset.x;
                y = mousePos.y - $.tableDnD.mouseOffset.y;

                // auto scroll the window
                $.tableDnD.autoScroll(mousePos);

                currentRow = $.tableDnD.findDropTargetRow(dragObj, y);
                moving = $.tableDnD.findDragDirection(x, y);

                $.tableDnD.moveVerticle(moving, currentRow);
                $.tableDnD.moveHorizontal(moving, currentRow);

                return false;
            },
            findDragDirection: function (x, y) {
                var sensitivity = this.currentTable.tableDnDConfig.sensitivity,
                    oldX = this.oldX,
                    oldY = this.oldY,
                    xMin = oldX - sensitivity,
                    xMax = oldX + sensitivity,
                    yMin = oldY - sensitivity,
                    yMax = oldY + sensitivity,
                    moving = {
                        horizontal: x >= xMin && x <= xMax ? 0 : x > oldX ? -1 : 1,
                        vertical: y >= yMin && y <= yMax ? 0 : y > oldY ? -1 : 1
                    };

                // update the old value
                if (moving.horizontal != 0)
                    this.oldX = x;
                if (moving.vertical != 0)
                    this.oldY = y;

                return moving;
            },
            /** We're only worried about the y position really, because we can only move rows up and down */
            findDropTargetRow: function (draggedRow, y) {
                var rowHeight = 0,
                    rows = this.currentTable.rows,
                    config = this.currentTable.tableDnDConfig,
                    rowY = 0,
                    row = null;
                for (var i = 0; i < rows.length; i++) {
                    row = rows[i];
                    rowY = this.getPosition(row).y;
                    rowHeight = parseInt(row.offsetHeight) / 2;
                    if (row.offsetHeight == 0) {
                        rowY = this.getPosition(row.firstChild).y;
                        rowHeight = parseInt(row.firstChild.offsetHeight) / 2;
                    }
                    // Because we always have to insert before, we need to offset the height a bit
                    if (y > (rowY - rowHeight) && y < (rowY + rowHeight))
                    // that's the row we're over
                    // If it's the same as the current row, ignore it
                       /* console.log(draggedRow.is(row));
                        console.log(config.onAllowDrop&& !config.onAllowDrop(draggedRow, row));
                        console.log($(row).hasClass("nodrop"));*/
                       /* var myDraggedRow="";
                       if(draggedRow instanceof Array){
                           myDraggedRow=draggedRow[0];
                       }else{
                           myDraggedRow=draggedRow;
                       }*/
                        if (draggedRow.is(row)
                            || (config.onAllowDrop
                            && !config.onAllowDrop(draggedRow, row))
                            // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
                            || $(row).hasClass("nodrop"))
                            return null;
                        else
                            return row;
                }
                return null;
            },
            processMouseup: function () {
                if (!this.currentTable || !this.dragObject)
                    return null;

                var config = this.currentTable.tableDnDConfig,
                    droppedRow = this.dragObject,
                    parentLevel = 0,
                    myLevel = 0;

                // Unbind the event handlers
                $(document)
                    .unbind(moveEvent, this.mousemove)
                    .unbind(endEvent, this.mouseup);

                config.hierarchyLevel
                && config.autoCleanRelations
                && $(this.currentTable.rows).first().find('td:first').children().each(function () {
                    myLevel = $(this).parents('tr:first').data('level');
                    myLevel
                    && $(this).parents('tr:first').data('level', --myLevel)
                    && $(this).remove();
                })
                && config.hierarchyLevel > 1
                && $(this.currentTable.rows).each(function () {
                    myLevel = $(this).data('level');
                    if (myLevel > 1) {
                        parentLevel = $(this).prev().data('level');
                        while (myLevel > parentLevel + 1) {
                            $(this).find('td:first').children(':first').remove();
                            $(this).data('level', --myLevel);
                        }
                    }
                });

                // If we have a dragObject, then we need to release it,
                // The row will already have been moved to the right place so we just reset stuff
                config.onDragClass
                && $(droppedRow).removeClass(config.onDragClass)
                || $(droppedRow).css(config.onDropStyle);

                this.dragObject = null;
                // Call the onDrop method if there is one
                config.onDrop
                && this.originalOrder != this.currentOrder()
                && $(droppedRow).hide().fadeIn('fast')
                && config.onDrop(this.currentTable, droppedRow);

                // Call the onDragStop method if there is one
                config.onDragStop
                && config.onDragStop(this.currentTable, droppedRow);

                this.currentTable = null; // let go of the table too
            },
            mouseup: function (e) {
                e && e.preventDefault();
                $.tableDnD.processMouseup();
                return false;
            },
            jsonize: function (pretify) {
                var table = this.currentTable;
                if (pretify)
                    return JSON.stringify(
                        this.tableData(table),
                        null,
                        table.tableDnDConfig.jsonPretifySeparator
                    );
                return JSON.stringify(this.tableData(table));
            },
            serialize: function () {
                return $.param(this.tableData(this.currentTable));
            },
            serializeTable: function (table) {
                var result = "";
                var paramName = table.tableDnDConfig.serializeParamName || table.id;
                var rows = table.rows;
                for (var i = 0; i < rows.length; i++) {
                    if (result.length > 0) result += "&";
                    var rowId = rows[i].id;
                    if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
                        rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
                        result += paramName + '[]=' + rowId;
                    }
                }
                return result;
            },
            serializeTables: function () {
                var result = [];
                $('table').each(function () {
                    this.id && result.push($.param(this.tableData(this)));
                });
                return result.join('&');
            },
            tableData: function (table) {
                var config = table.tableDnDConfig,
                    previousIDs = [],
                    currentLevel = 0,
                    indentLevel = 0,
                    rowID = null,
                    data = {},
                    getSerializeRegexp,
                    paramName,
                    currentID,
                    rows;

                if (!table)
                    table = this.currentTable;
                if (!table || !table.rows || !table.rows.length)
                    return {error: {code: 500, message: "Not a valid table."}};
                if (!table.id && !config.serializeParamName)
                    return {error: {code: 500, message: "No serializable unique id provided."}};

                rows = config.autoCleanRelations
                    && table.rows
                    || $.makeArray(table.rows);
                paramName = config.serializeParamName || table.id;
                currentID = paramName;

                getSerializeRegexp = function (rowId) {
                    if (rowId && config && config.serializeRegexp)
                        return rowId.match(config.serializeRegexp)[0];
                    return rowId;
                };

                data[currentID] = [];
                !config.autoCleanRelations
                && $(rows[0]).data('level')
                && rows.unshift({id: 'undefined'});


                for (var i = 0; i < rows.length; i++) {
                    if (config.hierarchyLevel) {
                        indentLevel = $(rows[i]).data('level') || 0;
                        if (indentLevel == 0) {
                            currentID = paramName;
                            previousIDs = [];
                        }
                        else if (indentLevel > currentLevel) {
                            previousIDs.push([currentID, currentLevel]);
                            currentID = getSerializeRegexp(rows[i - 1].id);
                        }
                        else if (indentLevel < currentLevel) {
                            for (var h = 0; h < previousIDs.length; h++) {
                                if (previousIDs[h][1] == indentLevel)
                                    currentID = previousIDs[h][0];
                                if (previousIDs[h][1] >= currentLevel)
                                    previousIDs[h][1] = 0;
                            }
                        }
                        currentLevel = indentLevel;

                        if (!$.isArray(data[currentID]))
                            data[currentID] = [];
                        rowID = getSerializeRegexp(rows[i].id);
                        rowID && data[currentID].push(rowID);
                    }
                    else {
                        rowID = getSerializeRegexp(rows[i].id);
                        rowID && data[currentID].push(rowID);
                    }
                }
                return data;
            }
        };

        jQuery.fn.extend(
            {
                tableDnD: $.tableDnD.build,
                tableDnDUpdate: $.tableDnD.updateTables,
                tableDnDSerialize: $.proxy($.tableDnD.serialize, $.tableDnD),
                tableDnDSerializeAll: $.tableDnD.serializeTables,
                tableDnDData: $.proxy($.tableDnD.tableData, $.tableDnD)
            }
        );

    }(jQuery, window, window.document);
    return jQuery;
});